package com.echo.code.thread.mode.listener;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

import static java.lang.Boolean.TRUE;

/**
 * Registry Created by baiyap on 2018/9/24.
 */
public class Registry {

    // 存储Subscriber集合和topic之间关系的map
    private ConcurrentHashMap<String, ConcurrentLinkedQueue<Subscriber>> subscriberContainer = new ConcurrentHashMap<>();

    public void bind(Object subscriber) {
        // 获取Subscriber Object的方法集合然后进行绑定
        List<Method> subscribeMethods = getSubscribeMethods(subscriber);
        subscribeMethods.forEach(m -> tieSubscriber(subscriber, m));
    }

    public void unbind(Object subscriber) {
        // unbind为了提高速度, 只对Subscriber进行失效操作
        subscriberContainer.forEach((key, queue) ->
                queue.forEach(s -> {
                    if (s.getSubscribeObject() == subscriber) {
                        s.setDisable(TRUE);
                    }
                }));
    }

    private List<Method> getSubscribeMethods(Object subscriber) {
        List<Method> methods = new ArrayList<>();
        Class<?> clazz = subscriber.getClass();
        // 获取所有方法
        Method[] declaredMethods = clazz.getDeclaredMethods();
        // 方法为public &&且有一个入参 &&且被@Subscribe标记的方法才符合回调方法
        Arrays.stream(declaredMethods).filter(m -> m.isAnnotationPresent(Subscribe.class) && m.getParameterCount() == 1
                && m.getModifiers() == Modifier.PUBLIC).forEach(methods::add);
        return methods;
    }

    private void tieSubscriber(Object subscriber, Method method) {
        Subscribe subscribe = method.getDeclaredAnnotation(Subscribe.class);
        String topic = subscribe.value();
        // 当某topic没有Subscriber Queue的时候创建一个
        subscriberContainer.computeIfAbsent(topic, key -> new ConcurrentLinkedQueue<>());
        // 创建一个Subscriber并且加入Subscriber列表中
        subscriberContainer.get(topic).add(new Subscriber(subscriber, method));
    }

    public ConcurrentLinkedQueue<Subscriber> scanSubscriber(String topic) {
        return subscriberContainer.get(topic);
    }
}